<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Spark • GLSL Shaders</title>
  <style>
    body { margin: 0; }
    canvas { touch-action: none; }
  </style>
</head>

<body>
  <script type="importmap">
    {
      "imports": {
        "three": "/examples/js/vendor/three/build/three.module.js",
        "lil-gui": "/examples/js/vendor/lil-gui/dist/lil-gui.esm.js",
        "@sparkjsdev/spark": "/dist/spark.module.js"
      }
    }
  </script>
  <script type="module">
    import * as THREE from "three";
    import { SparkControls, SplatMesh, dyno } from "@sparkjsdev/spark";
    import { getAssetFileURL } from "/examples/js/get-asset-url.js";
    import GUI from "lil-gui";

    // Scene setup
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    window.addEventListener('resize', onWindowResize, false);
    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }

    // Cat model setup
    const splatURL = await getAssetFileURL("cat.spz");
    const cat = new SplatMesh({ url: splatURL });
    cat.quaternion.set(1, 0, 0, 0);
    cat.position.set(0, 0, -1.5);
    cat.scale.set(.5, .5, .5);
    scene.add(cat);

    const animateT = dyno.dynoFloat(0);

    // Effect parameters and GUI
    const effectParams = {
      effect: "Disintegrate",
      intensity: 0.8
    };

    const gui = new GUI({ title: "Fractal Effects" });
    gui.add(effectParams, "effect", ["Electronic", "Deep Meditation", "Waves", "Disintegrate", "Flare"])
      .name("Effect Type")
      .onChange(() => cat.updateGenerator());
    gui.add(effectParams, "intensity", 0, 1, 0.01)
      .name("Intensity")
      .onChange(() => cat.updateGenerator());

    // Shader modifier setup
    cat.objectModifier = dyno.dynoBlock(
      { gsplat: dyno.Gsplat },
      { gsplat: dyno.Gsplat },
      ({ gsplat }) => {
        const d = new dyno.Dyno({
          inTypes: { 
            gsplat: dyno.Gsplat, 
            t: "float", 
            effectType: "int", 
            intensity: "float" 
          },
          outTypes: { gsplat: dyno.Gsplat },
          globals: () => [
            dyno.unindent(`
              vec3 hash(vec3 p) {
                return fract(sin(p*123.456)*123.456);
              }

              mat2 rot(float a) {
                float s = sin(a), c = cos(a);
                return mat2(c, -s, s, c);
              }

              vec3 headMovement(vec3 pos, float t) {
                pos.xy *= rot(smoothstep(-1., -2., pos.y) * .2 * sin(t*2.));
                return pos;
              }

              vec3 breathAnimation(vec3 pos, float t) {
                float b = sin(t*1.5);
                pos.yz *= rot(smoothstep(-1., -3., pos.y) * .15 * -b);
                pos.z += .3;
                pos.y += 1.2;
                pos *= 1. + exp(-3. * length(pos)) * b;
                pos.z -= .3;
                pos.y -= 1.2;
                return pos;
              }

              vec4 fractal1(vec3 pos, float t, float intensity) {
                float m = 100.;
                vec3 p = pos * .1;
                p.y += .5;
                for (int i = 0; i < 8; i++) {
                  p = abs(p) / clamp(abs(p.x * p.y), 0.3, 3.) - 1.;
                  p.xy *= rot(radians(90.));
                  if (i > 1) m = min(m, length(p.xy) + step(.3, fract(p.z * .5 + t * .5 + float(i) * .2)));
                }
                m = step(m, 0.5) * 1.3 * intensity;
                return vec4(-pos.y * .3, 0.5, 0.7, .3) * intensity + m;
              }

              vec4 fractal2(vec3 center, vec3 scales, vec4 rgba, float t, float intensity) {
                vec3 pos = center;
                float splatSize = length(scales);
                float pattern = exp(-50. * splatSize);
                vec3 p = pos * .65;
                pos.y += 2.;
                float c = 0.;
                float l, l2 = length(p);
                float m = 100.;
                
                for (int i = 0; i < 10; i++) {
                  p.xyz = abs(p.xyz) / dot(p.xyz, p.xyz) - .8;
                  l = length(p.xyz);
                  c += exp(-1. * abs(l - l2) * (1. + sin(t * 1.5 + pos.y)));
                  l2 = length(p.xyz);
                  m = min(m, length(p.xyz));
                }
                
                c = smoothstep(0.3, 0.5, m + sin(t * 1.5 + pos.y * .5)) + c * .1;              
                return vec4(vec3(length(rgba.rgb)) * vec3(c, c*c, c*c*c) * intensity, 
                          rgba.a * exp(-20. * splatSize) * m * intensity);
              }

              vec4 sin3D(vec3 p, float t) {
                float m = exp(-2. * length(sin(p * 5. + t * 3.))) * 5.;
                return vec4(m) + .3;
              }

              vec4 disintegrate(vec3 pos, float t, float intensity) {
                vec3 p = pos + (hash(pos) * 2. - 1.) * intensity;
                float tt = smoothstep(-1., 0.5, -sin(t + -pos.y * .5));  
                p.xz *= rot(tt * 2. + p.y * 2. * tt);
                return vec4(mix(p, pos, tt), tt);
              }
              
              vec4 flare(vec3 pos, float t) {
                vec3 p = vec3(0., -1.5, 0.);
                float tt = smoothstep(-1., .5, sin(t + hash(pos).x));  
                tt = tt * tt;              
                p.x += sin(t * 2.) * tt;
                p.z += sin(t * 2.) * tt;
                p.y += sin(t) * tt;
                return vec4(mix(pos, p, tt), tt);
              }
            `)
          ],
          statements: ({ inputs, outputs }) => dyno.unindentLines(`
            ${outputs.gsplat} = ${inputs.gsplat};
            
            vec3 localPos = ${inputs.gsplat}.center;
            vec3 splatScales = ${inputs.gsplat}.scales;
            vec4 splatColor = ${inputs.gsplat}.rgba;
            
            if (${inputs.effectType} == 1) {
              ${outputs.gsplat}.center = headMovement(localPos, ${inputs.t});
              vec4 effect1 = fractal1(localPos, ${inputs.t}, ${inputs.intensity});
              ${outputs.gsplat}.rgba.rgba = mix(splatColor, splatColor*effect1, ${inputs.intensity});
            } 
            else if (${inputs.effectType} == 2) {
              vec4 effectColor = fractal2(localPos, splatScales, splatColor, ${inputs.t}, ${inputs.intensity});
              ${outputs.gsplat}.rgba.rgba = mix(splatColor, effectColor, ${inputs.intensity});
              ${outputs.gsplat}.center = breathAnimation(localPos, ${inputs.t});
            } 
            else if (${inputs.effectType} == 3) {
              vec4 effect = sin3D(localPos, ${inputs.t});
              ${outputs.gsplat}.rgba.rgba = mix(splatColor, splatColor*effect, ${inputs.intensity});
              vec3 pos = localPos;
              pos.y += 1.;
              pos *= (1. + effect.x * .05 * ${inputs.intensity});
              pos.y -= 1.;
              ${outputs.gsplat}.center = pos;
            } 
            else if (${inputs.effectType} == 5) {
              vec4 e = disintegrate(localPos, ${inputs.t}, ${inputs.intensity});
              ${outputs.gsplat}.center = e.xyz;
              ${outputs.gsplat}.scales = mix(vec3(.01, .01, .01), ${inputs.gsplat}.scales, e.w);
            } 
            else if (${inputs.effectType} == 4) {
              vec4 e = flare(localPos, ${inputs.t});
              ${outputs.gsplat}.center = e.xyz;
              ${outputs.gsplat}.rgba.rgb = mix(splatColor.rgb, vec3(1.), abs(e.w));
              ${outputs.gsplat}.rgba.a = mix(splatColor.a, 0.3, abs(e.w));
            }
          `),
        });

        // Map effect names to type integers
        const effectTypeMap = {
          "Electronic": 1,
          "Deep Meditation": 2,
          "Waves": 3,
          "Flare": 4,
          "Disintegrate": 5
        };

        const effectType = effectTypeMap[effectParams.effect] || 1;
        
        gsplat = d.apply({ 
          gsplat, 
          t: animateT,
          effectType: dyno.dynoInt(effectType),
          intensity: dyno.dynoFloat(effectParams.intensity)
        }).gsplat;
        
        return { gsplat };
      }
    );

    cat.updateGenerator();

    // Animation loop
    const controls = new SparkControls({ canvas: renderer.domElement });
    renderer.setAnimationLoop(function animate(time) {
      animateT.value = time / 1000;
      cat.updateVersion();
      cat.position.set(0, -.7, -2.5);
      cat.rotation.set(Math.PI, time / 2000, 0);
      controls.update(camera);
      renderer.render(scene, camera);
    });
  </script>
</body>
</html>
